/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification of Java bytecode.
 *
 * Copyright (c) 2002-2018 GuardSquare NV
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package proguard.optimize.evaluation;

import proguard.classfile.*;
import proguard.classfile.constant.RefConstant;
import proguard.classfile.util.ClassUtil;
import proguard.evaluation.SimplifiedInvocationUnit;
import proguard.evaluation.value.*;
import proguard.optimize.info.ParameterEscapeMarker;

/**
 * This InvocationUnit tags reference values like {@link ReferenceTracingInvocationUnit}, but adds detail to return
 * values from invoked methods.
 *
 * @see ReferenceTracingInvocationUnit
 * @see TracedReferenceValue
 * @see InstructionOffsetValue
 * @author Eric Lafortune
 */
public class ParameterTracingInvocationUnit extends ReferenceTracingInvocationUnit {
    // *
    private static final boolean DEBUG = false;
    /*/
    private static       boolean DEBUG = System.getProperty("ptiu") != null;
    //*/

    private Value[] parameters = new Value[256];

    /**
     * Creates a new ParameterTracingInvocationUnit that attaches trace values specifying the parameter index to each
     * parameter.
     * 
     * @param invocationUnit the invocation unit to which invocations will be delegated.
     */
    public ParameterTracingInvocationUnit(SimplifiedInvocationUnit invocationUnit) {
        super(invocationUnit);
    }

    // Implementations for SimplifiedInvocationUnit.

    public void setMethodParameterValue(Clazz clazz, RefConstant refConstant, int parameterIndex, Value value) {
        super.setMethodParameterValue(clazz, refConstant, parameterIndex, value);

        parameters[parameterIndex] = value;
    }

    public Value getMethodReturnValue(Clazz clazz, RefConstant refConstant, String type) {
        Value returnValue = super.getMethodReturnValue(clazz, refConstant, type);

        // We only need to worry about reference values.
        if (returnValue.computationalType() != Value.TYPE_REFERENCE) {
            return returnValue;
        }

        Method referencedMethod = (Method)refConstant.referencedMember;
        if (referencedMethod != null) {
            // Start figuring out which trace value to attach to the return value.
            int offset =
                ((TracedReferenceValue)returnValue).getTraceValue().instructionOffsetValue().instructionOffset(0);

            // The trace value might be any external value or just a new instance.
            InstructionOffsetValue traceValue = ParameterEscapeMarker.returnsExternalValues(referencedMethod)
                ? new InstructionOffsetValue(offset | InstructionOffsetValue.FIELD_VALUE)
                : ParameterEscapeMarker.returnsNewInstances(referencedMethod)
                    ? new InstructionOffsetValue(offset | InstructionOffsetValue.NEW_INSTANCE) : null;

            long returnedParameters = ParameterEscapeMarker.getReturnedParameters(referencedMethod);

            int parameterCount = ClassUtil.internalMethodParameterCount(refConstant.getType(clazz), isStatic);

            for (int parameterIndex = 0; parameterIndex < parameterCount; parameterIndex++) {
                if ((returnedParameters & (1L << parameterIndex)) != 0L) {
                    Value parameterValue = parameters[parameterIndex];
                    if (parameterValue instanceof TracedReferenceValue) {
                        TracedReferenceValue tracedParameterValue = (TracedReferenceValue)parameterValue;

                        if (mayReturnType(refConstant.referencedClass, referencedMethod, tracedParameterValue)) {
                            InstructionOffsetValue parameterTraceValue =
                                tracedParameterValue.getTraceValue().instructionOffsetValue();

                            traceValue =
                                traceValue == null ? parameterTraceValue : traceValue.generalize(parameterTraceValue);
                        }
                    }
                }
            }

            if (DEBUG) {
                System.out.println("ParameterTracingInvocationUnit.getMethodReturnValue: calling ["
                    + refConstant.getClassName(clazz) + "." + refConstant.getName(clazz) + refConstant.getType(clazz)
                    + "] returns [" + traceValue + " " + returnValue + "]");
            }

            // Did we find more detailed information on the return value?
            // We should, unless the return value is always null.
            if (traceValue != null) {
                return trace(returnValue, traceValue);
            }
        }

        return returnValue;
    }

    // Small utility methods.

    /**
     * Returns whether the given method may return the given type of reference value
     */
    private boolean mayReturnType(Clazz clazz, Method method, ReferenceValue referenceValue) {
        String returnType = ClassUtil.internalMethodReturnType(method.getDescriptor(clazz));

        Clazz[] referencedClasses = method instanceof ProgramMethod ? ((ProgramMethod)method).referencedClasses
            : ((LibraryMethod)method).referencedClasses;

        Clazz referencedClass = referencedClasses == null || !ClassUtil.isInternalClassType(returnType) ? null
            : referencedClasses[referencedClasses.length - 1];

        return referenceValue.instanceOf(returnType, referencedClass) != Value.NEVER;
    }
}